﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace WindowsGame1
{
    class Terreno
    {

        #region Atributos
        private struct VertexPositionColorNormal
        {
            public Vector3 Position;
            public Color Color;
            public Vector3 Normal;

            public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
            (
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
            );
        }

        private VertexPositionColorNormal[] vertices;
        private int[] indices;

        private int terrainWidth;
        private int terrainHeight;
        private float[,] heightData;
        

        Matrix viewMatrix;
        Matrix projectionMatrix;

        VertexBuffer myVertexBuffer;
        IndexBuffer myIndexBuffer;

        GraphicsDevice device;
        Effect effect;

        private float angle = 0f;
        private float zoom = 1f;
        Vector3 pos = Vector3.Zero;

        #endregion

        public Terreno(Texture2D heightMap, GraphicsDevice device, Effect effect)
        {

            this.device = device;
            this.effect = effect;

            LoadHeightData(heightMap);

            SetUpIndices();
            SetUpVertices();
            CalculateNormals();

            CopyToBuffers();

            SetUpCamera();
        }

        private void SetUpCamera()
        {
            viewMatrix = Matrix.CreateLookAt(new Vector3(60, 80, -80), new Vector3(-1.2f, 20.85f, 52.15f), new Vector3(0, 1, 0));
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 1.0f, 300.0f);
        }


        public void Draw(GameTime gameTime)
        {

            effect.CurrentTechnique = effect.Techniques["Colored"];

            //And turn on the light:
            Vector3 lightDirection = new Vector3(1.0f, -1.0f, -1.0f);
            lightDirection.Normalize();
            effect.Parameters["xLightDirection"].SetValue(lightDirection);
            effect.Parameters["xAmbient"].SetValue(0.1f); effect.Parameters["xEnableLighting"].SetValue(true);

            effect.Parameters["xView"].SetValue(viewMatrix);
            effect.Parameters["xProjection"].SetValue(projectionMatrix);

            Matrix worldMatrix = Matrix.CreateTranslation(pos) * Matrix.CreateRotationY(angle) * Matrix.CreateScale(zoom);

            effect.Parameters["xWorld"].SetValue(worldMatrix);

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
            }
            device.Indices = myIndexBuffer;
            device.SetVertexBuffer(myVertexBuffer);
            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);


            /*
            float minHeightPouso = float.MaxValue;
            float maxHeightPouso = float.MinValue;
            if (-posTerrain.X >= areaPouso && -posTerrain.X < terrainWidth - areaPouso && posTerrain.Z >= areaPouso && posTerrain.Z < terrainHeight - areaPouso)
            {
                for (int x = (int)(-posTerrain.X) - areaPouso; x <= (int)(-posTerrain.X) + areaPouso; x++)
                {
                    for (int y = (int)(posTerrain.Z) - areaPouso; y <= (int)(posTerrain.Z) + areaPouso; y++)
                    {
                        if (heightData[x, y] < minHeightPouso)
                            minHeightPouso = heightData[x, y];
                        if (heightData[x, y] > maxHeightPouso)
                            maxHeightPouso = heightData[x, y];
                    }
                }
                h = heightData[-(int)posTerrain.X, (int)posTerrain.Z].ToString() + ", diff pouso:" + minHeightPouso.ToString() + " a " + maxHeightPouso.ToString();
            }*/

        }
        
        public void Update(GameTime gameTime, KeyboardState keyState)
        {
            if (keyState.IsKeyDown(Keys.Left))
                angle += 0.05f;
            if (keyState.IsKeyDown(Keys.Right))
                angle -= 0.05f; 
            
            
            if (keyState.IsKeyDown(Keys.Up))
                pos.Y += 0.05f;
            if (keyState.IsKeyDown(Keys.Down))
                pos.Y -= 0.05f;

            if (keyState.IsKeyDown(Keys.S))
                pos.Z += 0.05f;
            if (keyState.IsKeyDown(Keys.W))
                pos.Z -= 0.05f;

            if (keyState.IsKeyDown(Keys.A))
                pos.X += 0.05f;
            if (keyState.IsKeyDown(Keys.D))
                pos.X -= 0.05f;
        }

        private void LoadHeightData(Texture2D heightMap)
        {
            terrainWidth = heightMap.Width;
            terrainHeight = heightMap.Height;

            Color[] heightMapColors = new Color[terrainWidth * terrainHeight];
            heightMap.GetData(heightMapColors);

            heightData = new float[terrainWidth, terrainHeight];
            for (int x = 0; x < terrainWidth; x++)
                for (int y = 0; y < terrainHeight; y++)
                    heightData[x, y] = heightMapColors[x + y * terrainWidth].R / 5.0f;
        }

        private void SetUpIndices()
        {
            indices = new int[(terrainWidth - 1) * (terrainHeight - 1) * 6];
            int counter = 0;
            for (int y = 0; y < terrainHeight - 1; y++)
            {
                for (int x = 0; x < terrainWidth - 1; x++)
                {
                    int lowerLeft = x + y * terrainWidth;
                    int lowerRight = (x + 1) + y * terrainWidth;
                    int topLeft = x + (y + 1) * terrainWidth;
                    int topRight = (x + 1) + (y + 1) * terrainWidth;

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }
        }

        private void SetUpVertices()
        {
            float minHeight = float.MaxValue;
            float maxHeight = float.MinValue;
            for (int x = 0; x < terrainWidth; x++)
            {
                for (int y = 0; y < terrainHeight; y++)
                {
                    if (heightData[x, y] < minHeight)
                        minHeight = heightData[x, y];
                    if (heightData[x, y] > maxHeight)
                        maxHeight = heightData[x, y];
                }
            }

            vertices = new VertexPositionColorNormal[terrainWidth * terrainHeight];
            for (int x = 0; x < terrainWidth; x++)
            {
                for (int y = 0; y < terrainHeight; y++)
                {
                    //vertices[x + y * terrainWidth].Position = new Vector3(x, 0, -y);
                    vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y);

                    //vertices[x + y * terrainWidth].Color = Color.WhiteSmoke;


                    if (heightData[x, y] < minHeight + (maxHeight - minHeight) / 4)
                        vertices[x + y * terrainWidth].Color = Color.DarkGreen;

                    else if (heightData[x, y] < minHeight + (maxHeight - minHeight) * 2 / 4)
                        vertices[x + y * terrainWidth].Color = Color.CornflowerBlue;

                    else if (heightData[x, y] < minHeight + (maxHeight - minHeight) * 3 / 4)
                        vertices[x + y * terrainWidth].Color = Color.White;

                    else
                        vertices[x + y * terrainWidth].Color = Color.SpringGreen;

                }
            }
        }

        private void CalculateNormals()
        {
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal = new Vector3(0, 0, 0);

            for (int i = 0; i < indices.Length / 3; i++)
            {
                int index1 = indices[i * 3];
                int index2 = indices[i * 3 + 1];
                int index3 = indices[i * 3 + 2];

                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);

                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;
            }
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();
        }

        private void CopyToBuffers()
        {
            myVertexBuffer = new VertexBuffer(device, VertexPositionColorNormal.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
            myVertexBuffer.SetData(vertices);
            myIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly);
            myIndexBuffer.SetData(indices);
        }
    }
}
